diff options
Diffstat (limited to 'src/routes/user/[user]/badges/+page.svelte')
| -rw-r--r-- | src/routes/user/[user]/badges/+page.svelte | 634 |
1 files changed, 333 insertions, 301 deletions
diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte index 4a5106aa..5a52b285 100644 --- a/src/routes/user/[user]/badges/+page.svelte +++ b/src/routes/user/[user]/badges/+page.svelte @@ -1,57 +1,61 @@ <script lang="ts"> -import Spacer from '$lib/Layout/Spacer.svelte'; -import AWC from './../../../../lib/User/BadgeWall/AWC.svelte'; -import { user, type User } from '$lib/Data/AniList/user'; -import type { Badge } from '../../../../graphql/$types'; -import { onDestroy, onMount } from 'svelte'; -import HeadTitle from '$lib/Home/HeadTitle.svelte'; -import { databaseTimeToDate, dateToInputTime, inputTimeToDatabaseTime } from '$lib/Utility/time'; -import proxy from '$lib/Utility/proxy'; -import locale from '$stores/locale'; -import Skeleton from '$lib/Loading/Skeleton.svelte'; -import Message from '$lib/Loading/Message.svelte'; -import Dropdown from '$lib/Layout/Dropdown.svelte'; -import { activityText } from '$lib/Data/AniList/activity'; -import SettingHint from '$lib/Settings/SettingHint.svelte'; -import Popup from '$lib/Layout/Popup.svelte'; -import { page } from '$app/stores'; -import { browser } from '$app/environment'; -import BadgePreview from '$lib/User/BadgeWall/BadgePreview.svelte'; -import authorisedJson from '$lib/Data/Static/authorised.json'; -import identity from '$stores/identity'; -import '$lib/User/BadgeWall/badges.css'; -import Badges from '$lib/User/BadgeWall/Badges.svelte'; -import type { IndexedBadge } from '$lib/User/BadgeWall/badge'; -import { graphql } from '$houdini'; -import type { Preferences } from '../../../../graphql/$types'; -import localforage from 'localforage'; -import type { PageData } from './$types'; +import Spacer from "$lib/Layout/Spacer.svelte"; +import AWC from "./../../../../lib/User/BadgeWall/AWC.svelte"; +import { user, type User } from "$lib/Data/AniList/user"; +import type { Badge } from "../../../../graphql/$types"; +import { onDestroy, onMount } from "svelte"; +import HeadTitle from "$lib/Home/HeadTitle.svelte"; +import { + databaseTimeToDate, + dateToInputTime, + inputTimeToDatabaseTime, +} from "$lib/Utility/time"; +import proxy from "$lib/Utility/proxy"; +import locale from "$stores/locale"; +import Skeleton from "$lib/Loading/Skeleton.svelte"; +import Message from "$lib/Loading/Message.svelte"; +import Dropdown from "$lib/Layout/Dropdown.svelte"; +import { activityText } from "$lib/Data/AniList/activity"; +import SettingHint from "$lib/Settings/SettingHint.svelte"; +import Popup from "$lib/Layout/Popup.svelte"; +import { page } from "$app/stores"; +import { browser } from "$app/environment"; +import BadgePreview from "$lib/User/BadgeWall/BadgePreview.svelte"; +import authorisedJson from "$lib/Data/Static/authorised.json"; +import identity from "$stores/identity"; +import "$lib/User/BadgeWall/badges.css"; +import Badges from "$lib/User/BadgeWall/Badges.svelte"; +import type { IndexedBadge } from "$lib/User/BadgeWall/badge"; +import { graphql } from "$houdini"; +import type { Preferences } from "../../../../graphql/$types"; +import localforage from "localforage"; +import type { PageData } from "./$types"; export let data: PageData; $: ({ BadgeWallUser } = data); $: preferences = $BadgeWallUser.fetching - ? undefined - : ($BadgeWallUser.data?.User?.preferences as Preferences | undefined); + ? undefined + : ($BadgeWallUser.data?.User?.preferences as Preferences | undefined); $: if (browser && preferences && preferences.badge_wall_css) { - const sanitise = (css: string) => - css - .replace(/\/\*[\s\S]*?\*\//g, '') - .replace(/<\/?[^>]+(>|$)/g, '') - .replace( - /(expression|javascript|vbscript|onerror|onload|onclick|onmouseover|onmouseout|onmouseup|onmousedown|onkeydown|onkeyup|onkeypress|onblur|onfocus|onsubmit|onreset|onselect|onchange|ondblclick):/gi, - '' - ) - .replace(/(behaviour|behavior|moz-binding|content):/gi, '') - .replace(/\s+/g, ' ') - .trim(); - const style = document.createElement('style'); - - style.dataset.badgeWall = 'true'; - style.innerHTML = sanitise(preferences.badge_wall_css); - - document.head.appendChild(style); + const sanitise = (css: string) => + css + .replace(/\/\*[\s\S]*?\*\//g, "") + .replace(/<\/?[^>]+(>|$)/g, "") + .replace( + /(expression|javascript|vbscript|onerror|onload|onclick|onmouseover|onmouseout|onmouseup|onmousedown|onkeydown|onkeyup|onkeypress|onblur|onfocus|onsubmit|onreset|onselect|onchange|ondblclick):/gi, + "", + ) + .replace(/(behaviour|behavior|moz-binding|content):/gi, "") + .replace(/\s+/g, " ") + .trim(); + const style = document.createElement("style"); + + style.dataset.badgeWall = "true"; + style.innerHTML = sanitise(preferences.badge_wall_css); + + document.head.appendChild(style); } const updateBadgeQuery = graphql(` @@ -175,8 +179,8 @@ const shadowHideBadgeQuery = graphql(` `); interface ImportImage { - link?: string; - image: string; + link?: string; + image: string; } let editMode = false; @@ -190,7 +194,7 @@ let loadError: string | null = null; const isId = /^\d+$/.test(data.username); let importImages: ImportImage[] | undefined = undefined; let importLinks = false; -let importCategory = ''; +let importCategory = ""; let importReplies = false; let badger: Partial<User> | null; let migrateMode = false; @@ -198,313 +202,341 @@ let hideMode = false; const authorised = authorisedJson.includes($identity.id); let noticeDismissed = false; -$: categoryFilter = new URLSearchParams($page.url.searchParams).get('category'); -$: loadQueryParameter = new URLSearchParams($page.url.searchParams).get('load'); +$: categoryFilter = new URLSearchParams($page.url.searchParams).get("category"); +$: loadQueryParameter = new URLSearchParams($page.url.searchParams).get("load"); type GroupedBadges = { [key: string]: IndexedBadge[] }; const setShadowHide = () => { - if (!badger) { - loadError = 'Something went wrong. Try refreshing.'; + if (!badger) { + loadError = "Something went wrong. Try refreshing."; - return; - } + return; + } - shadowHideBadgeQuery.mutate({ - id: badger.id as number - }); + shadowHideBadgeQuery.mutate({ + id: badger.id as number, + }); }; onMount(async () => { - if (browser && (await localforage.getItem('badgeWallNoticeDismissed'))) noticeDismissed = true; + if (browser && (await localforage.getItem("badgeWallNoticeDismissed"))) + noticeDismissed = true; - badger = isId - ? { - id: parseInt(data.username), - name: 'User' - } - : await user(data.username); + badger = isId + ? { + id: parseInt(data.username), + name: "User", + } + : await user(data.username); - if (!badger) { - loadError = "Couldn't find this user."; + if (!badger) { + loadError = "Couldn't find this user."; - return; - } + return; + } - awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`)); + awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`)); }); onDestroy(() => { - if (browser) - Array.from(document.head.querySelectorAll('style')).forEach((style) => { - if (style.dataset.badgeWall) style.remove(); - }); + if (browser) + Array.from(document.head.querySelectorAll("style")).forEach((style) => { + if (style.dataset.badgeWall) style.remove(); + }); }); const submitBadge = () => { - const imageURL = document.querySelector('input[name="image_url"]') as HTMLInputElement; - const activityURL = document.querySelector('input[name="activity_url"]') as HTMLInputElement; - const description = document.querySelector('input[name="description"]') as HTMLInputElement; - const time = document.querySelector('input[type="datetime-local"]') as HTMLInputElement; - const category = document.querySelector('input[name="category"]') as HTMLInputElement; - const hidden = document.querySelector('input[name="hidden"]') as HTMLInputElement; - const source = document.querySelector('input[name="source"]') as HTMLInputElement; - const designer = document.querySelector('input[name="designer"]') as HTMLInputElement; - - if (!imageURL.value) { - error = 'Image URL cannot be empty.'; - - return; - } - - if ( - !imageURL.value.startsWith('http') || - (activityURL.value.length > 0 && !activityURL.value.startsWith('http')) - ) { - error = 'URLs must start with http or https.'; - - return; - } - - updateBadgeQuery - .mutate({ - id: selectedBadge?.id, - image: imageURL.value, - post: activityURL.value || '#', - description: description.value, - category: category.value, - time: time.value ? inputTimeToDatabaseTime(new Date(time.value)) : undefined, - hidden: hidden.value === 'Hidden', - source: source.value, - designer: designer.value - }) - .then(() => { - error = null; - imageURL.value = ''; - activityURL.value = ''; - description.value = ''; - category.value = ''; - hidden.value = 'Shown'; - selectedBadge = undefined; - source.value = ''; - designer.value = ''; - }); + const imageURL = document.querySelector( + 'input[name="image_url"]', + ) as HTMLInputElement; + const activityURL = document.querySelector( + 'input[name="activity_url"]', + ) as HTMLInputElement; + const description = document.querySelector( + 'input[name="description"]', + ) as HTMLInputElement; + const time = document.querySelector( + 'input[type="datetime-local"]', + ) as HTMLInputElement; + const category = document.querySelector( + 'input[name="category"]', + ) as HTMLInputElement; + const hidden = document.querySelector( + 'input[name="hidden"]', + ) as HTMLInputElement; + const source = document.querySelector( + 'input[name="source"]', + ) as HTMLInputElement; + const designer = document.querySelector( + 'input[name="designer"]', + ) as HTMLInputElement; + + if (!imageURL.value) { + error = "Image URL cannot be empty."; + + return; + } + + if ( + !imageURL.value.startsWith("http") || + (activityURL.value.length > 0 && !activityURL.value.startsWith("http")) + ) { + error = "URLs must start with http or https."; + + return; + } + + updateBadgeQuery + .mutate({ + id: selectedBadge?.id, + image: imageURL.value, + post: activityURL.value || "#", + description: description.value, + category: category.value, + time: time.value + ? inputTimeToDatabaseTime(new Date(time.value)) + : undefined, + hidden: hidden.value === "Hidden", + source: source.value, + designer: designer.value, + }) + .then(() => { + error = null; + imageURL.value = ""; + activityURL.value = ""; + description.value = ""; + category.value = ""; + hidden.value = "Shown"; + selectedBadge = undefined; + source.value = ""; + designer.value = ""; + }); }; const removeAllBadges = () => { - if (confirmPrune === 2) { - confirmPrune = 0; - } else if (confirmPrune === 0) { - confirmPrune = 1; + if (confirmPrune === 2) { + confirmPrune = 0; + } else if (confirmPrune === 0) { + confirmPrune = 1; - return; - } else { - confirmPrune = 2; + return; + } else { + confirmPrune = 2; - return; - } + return; + } - selectedBadge = undefined; + selectedBadge = undefined; - pruneBadgesQuery.mutate(null).then(); + pruneBadgesQuery.mutate(null).then(); }; const removeBadge = (badge: Badge) => { - if (!badge.id) return; + if (!badge.id) return; - if (confirmDelete === badge.id * 2) { - confirmDelete = 0; - } else if (confirmDelete / 4 === badge.id) { - confirmDelete = badge.id * 2; + if (confirmDelete === badge.id * 2) { + confirmDelete = 0; + } else if (confirmDelete / 4 === badge.id) { + confirmDelete = badge.id * 2; - return; - } else { - confirmDelete = badge.id * 2; + return; + } else { + confirmDelete = badge.id * 2; - return; - } + return; + } - selectedBadge = undefined; + selectedBadge = undefined; - deleteBadgeQuery - .mutate({ - id: badge.id - }) - .then(); + deleteBadgeQuery + .mutate({ + id: badge.id, + }) + .then(); }; const groupBadges = (badges: IndexedBadge[]) => { - const groupedBadges: GroupedBadges = {}; - - badges.forEach((badge) => { - if (!badge.category) badge.category = 'Uncategorised'; - - if (!groupedBadges[badge.category]) groupedBadges[badge.category] = []; - - groupedBadges[badge.category].push(badge); - }); - - Object.entries(groupedBadges).forEach(([_categoryKey, badges]) => { - badges.forEach((badge, index) => { - badge.index = index; - }); - }); - - return Object.entries(groupedBadges) - .sort((a, b) => a[1].length - b[1].length) - .sort((a, b) => { - const pinnedCategories = - preferences && preferences.pinned_badge_wall_categories - ? preferences.pinned_badge_wall_categories - : ([] as string[]); - const aIndex = pinnedCategories.indexOf(a[0]); - const bIndex = pinnedCategories.indexOf(b[0]); - - if (aIndex === -1 && bIndex === -1) return 0; - if (aIndex === -1) return 1; - if (bIndex === -1) return -1; - - return aIndex - bIndex; - }) - .reduce((set: GroupedBadges, [key, value]) => { - set[key] = value; - - return set; - }, {}); + const groupedBadges: GroupedBadges = {}; + + badges.forEach((badge) => { + if (!badge.category) badge.category = "Uncategorised"; + + if (!groupedBadges[badge.category]) groupedBadges[badge.category] = []; + + groupedBadges[badge.category].push(badge); + }); + + Object.entries(groupedBadges).forEach(([_categoryKey, badges]) => { + badges.forEach((badge, index) => { + badge.index = index; + }); + }); + + return Object.entries(groupedBadges) + .sort((a, b) => a[1].length - b[1].length) + .sort((a, b) => { + const pinnedCategories = + preferences && preferences.pinned_badge_wall_categories + ? preferences.pinned_badge_wall_categories + : ([] as string[]); + const aIndex = pinnedCategories.indexOf(a[0]); + const bIndex = pinnedCategories.indexOf(b[0]); + + if (aIndex === -1 && bIndex === -1) return 0; + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + + return aIndex - bIndex; + }) + .reduce((set: GroupedBadges, [key, value]) => { + set[key] = value; + + return set; + }, {}); }; const parsePost = async () => { - if (importImages && importImages.length > 0) importImages = undefined; - - const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value; - const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1'); - const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2'); - - if (type !== 'activity') return null; - - let text = await activityText(parseInt(id), importReplies); - - const images: ImportImage[] = []; - - if (importLinks) { - Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach( - (a) => { - const anchor = a as HTMLAnchorElement; - - if (anchor.querySelector('img')) { - images.push({ - link: anchor.href, - image: (anchor.querySelector('img') as HTMLImageElement).src - }); - } - } - ); - - text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, ''); - - Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')).forEach( - (img) => { - const image = img as HTMLImageElement; - - images.push({ - image: image.src - }); - } - ); - } else { - Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')).forEach( - (img) => { - const image = img as HTMLImageElement; - - images.push({ - image: image.src - }); - } - ); - } - - importImages = images; + if (importImages && importImages.length > 0) importImages = undefined; + + const link = ( + document.querySelector("#import_activity_url") as HTMLInputElement + ).value; + const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, "$1"); + const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, "$2"); + + if (type !== "activity") return null; + + let text = await activityText(parseInt(id), importReplies); + + const images: ImportImage[] = []; + + if (importLinks) { + Array.from( + new DOMParser().parseFromString(text, "text/html").querySelectorAll("a"), + ).forEach((a) => { + const anchor = a as HTMLAnchorElement; + + if (anchor.querySelector("img")) { + images.push({ + link: anchor.href, + image: (anchor.querySelector("img") as HTMLImageElement).src, + }); + } + }); + + text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, ""); + + Array.from( + new DOMParser() + .parseFromString(text, "text/html") + .querySelectorAll("img"), + ).forEach((img) => { + const image = img as HTMLImageElement; + + images.push({ + image: image.src, + }); + }); + } else { + Array.from( + new DOMParser() + .parseFromString(text, "text/html") + .querySelectorAll("img"), + ).forEach((img) => { + const image = img as HTMLImageElement; + + images.push({ + image: image.src, + }); + }); + } + + importImages = images; }; const importBadges = () => - fetch( - `/api/badges?import=true - ${importCategory.length > 0 ? `&category=${encodeURIComponent(importCategory)}` : ''} + fetch( + `/api/badges?import=true + ${importCategory.length > 0 ? `&category=${encodeURIComponent(importCategory)}` : ""} `, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify( - importImages?.map((image) => ({ - image: image.image, - post: image.link || '#', - category: importCategory - })) - ) - } - ).then(() => { - importMode = false; - importImages = undefined; - }); + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify( + importImages?.map((image) => ({ + image: image.image, + post: image.link || "#", + category: importCategory, + })), + ), + }, + ).then(() => { + importMode = false; + importImages = undefined; + }); const migrateCategory = () => { - fetch( - `/api/badges?migrate=true&original=${encodeURIComponent( - (document.querySelector('#migrate_original') as HTMLInputElement).value - )}&new=${encodeURIComponent( - (document.querySelector('#migrate_new') as HTMLInputElement).value - )}`, - { - method: 'PUT' - } - ).then(() => (migrateMode = false)); + fetch( + `/api/badges?migrate=true&original=${encodeURIComponent( + (document.querySelector("#migrate_original") as HTMLInputElement).value, + )}&new=${encodeURIComponent( + (document.querySelector("#migrate_new") as HTMLInputElement).value, + )}`, + { + method: "PUT", + }, + ).then(() => (migrateMode = false)); }; const hideCategory = () => { - hideCategoryQuery - .mutate({ - category: (document.querySelector('#category_hide') as HTMLInputElement).value - }) - .then(() => (hideMode = false)); + hideCategoryQuery + .mutate({ + category: (document.querySelector("#category_hide") as HTMLInputElement) + .value, + }) + .then(() => (hideMode = false)); }; const removeHiddenBadges = (isOwner: boolean, badges: IndexedBadge[]) => - isOwner || authorised ? badges : badges.filter((b) => !b.hidden && !b.shadow_hidden); + isOwner || authorised + ? badges + : badges.filter((b) => !b.hidden && !b.shadow_hidden); const setAdjacentCursor = (badges: IndexedBadge[], direction: number) => { - const currentCategory = selectedBadge?.category || 'Uncategorised'; - const currentBadge = selectedBadge?.index; - const categoryBadges = groupBadges(badges)[currentCategory]; + const currentCategory = selectedBadge?.category || "Uncategorised"; + const currentBadge = selectedBadge?.index; + const categoryBadges = groupBadges(badges)[currentCategory]; - if (!currentCategory || currentBadge === undefined) return; + if (!currentCategory || currentBadge === undefined) return; - let previousBadge = categoryBadges[currentBadge + direction]; + let previousBadge = categoryBadges[currentBadge + direction]; - while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) - previousBadge = categoryBadges[previousBadge.index + direction]; + while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) + previousBadge = categoryBadges[previousBadge.index + direction]; - if (previousBadge) selectedBadge = previousBadge; + if (previousBadge) selectedBadge = previousBadge; }; const adjacentBadgeExists = ( - selectedBadge: IndexedBadge | undefined, - badges: IndexedBadge[], - direction: number + selectedBadge: IndexedBadge | undefined, + badges: IndexedBadge[], + direction: number, ) => { - const currentCategory = selectedBadge?.category || 'Uncategorised'; - const currentBadge = selectedBadge?.index; - const categoryBadges = groupBadges(badges)[currentCategory]; + const currentCategory = selectedBadge?.category || "Uncategorised"; + const currentBadge = selectedBadge?.index; + const categoryBadges = groupBadges(badges)[currentCategory]; - if (!currentCategory || currentBadge === undefined || !categoryBadges) return; + if (!currentCategory || currentBadge === undefined || !categoryBadges) return; - let previousBadge = categoryBadges[currentBadge + direction]; + let previousBadge = categoryBadges[currentBadge + direction]; - while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) - previousBadge = categoryBadges[previousBadge.index + direction]; + while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) + previousBadge = categoryBadges[previousBadge.index + direction]; - return previousBadge; + return previousBadge; }; const castAsStringArray = (array: unknown[]) => array as string[]; @@ -512,20 +544,20 @@ const castAsStringArray = (array: unknown[]) => array as string[]; const castBadgesToIndexedBadges = (array: unknown[]) => array as IndexedBadge[]; const shadowHideBadge = () => { - if (!selectedBadge && !authorised) return; + if (!selectedBadge && !authorised) return; - if (!badger) { - loadError = 'Something went wrong. Try refreshing.'; + if (!badger) { + loadError = "Something went wrong. Try refreshing."; - return; - } + return; + } - shadowHideBadgeQuery - .mutate({ - id: badger.id as number, - state: selectedBadge?.shadow_hidden as boolean - }) - .then(); + shadowHideBadgeQuery + .mutate({ + id: badger.id as number, + state: selectedBadge?.shadow_hidden as boolean, + }) + .then(); }; </script> |